﻿/*

The MIT License

Copyright (c) 2010 Cartesian Analytics, Inc. 

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

using System;
using System.Diagnostics;

namespace Pipra.Math.Geometry
{
    /// <summary>
    /// A vector in 2D space.
    /// </summary>
    public struct Vector2 :
        IVector2<double>,
        IEquatable<Vector2>,
        IComparable<Vector2>
    {
        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="a">A vector.</param>
        /// <param name="b">A vector.</param>
        /// <returns>True if both vectors have the same component values.</returns>
        public static bool operator ==(Vector2 a, Vector2 b)
        {
            return a.Equals(b);
        }
        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="a">A vector.</param>
        /// <param name="b">A vector.</param>
        /// <returns>True if both vectors do not have the same component values.</returns>
        public static bool operator !=(Vector2 a, Vector2 b)
        {
            return !a.Equals(b);
        }

        /// <summary>
        /// A vector with all components set to zero.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public static readonly Vector2 ZeroVector = new Vector2(0, 0);
        /// <summary>
        /// A vector with a magnitude of one and oriented in the direction of the positive X axis.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public static readonly Vector2 XUnitVector = new Vector2(1, 0);
        /// <summary>
        /// A vector with a magnitude of one and oriented in the direction of the positive Y axis.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public static readonly Vector2 YUnitVector = new Vector2(0, 1);

        /// <summary>
        /// The X componenet of this point.
        /// </summary>
        public readonly double X;
        /// <summary>
        /// The Y componenet of this point.
        /// </summary>
        public readonly double Y;

        /// <summary>
        /// Creates a 2D vector.
        /// </summary>
        /// <param name="x">The x coordinate.</param>
        /// <param name="y">The y coordinate.</param>
        public Vector2(double x, double y) {
			X = x;
			Y = y;
		}
        /// <summary>
        /// Creates a 2D vector.
        /// </summary>
        /// <param name="v">The coordinate tuple to copy values from.</param>
		public Vector2(ICoordinatePair<double> v) {
			X = v.X;
			Y = v.Y;
		}

        /// <summary>
        /// The X componenet of this point.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        double ICoordinatePair<double>.X
        {
            get { return X; }
        }
        /// <summary>
        /// The Y componenet of this point.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        double ICoordinatePair<double>.Y
        {
            get { return Y; }
        }

        /// <summary>
        /// Calculates the dot product between this vector and another vector defined by <paramref name="x"/> and <paramref name="y"/>.
        /// </summary>
        /// <param name="x">The x-coordinate of another vector.</param>
        /// <param name="y">The y-coordinate of another vector.</param>
        /// <returns>The dot product.</returns>
        public double Dot(double x, double y)
        {
            return (X * x) + (Y * y);
        }

        /// <summary>
        /// Calculates the dot product between this vector and another vector.
        /// </summary>
        /// <param name="rhs">Another vector to use for the calculation of the dot product.</param>
        /// <returns>The dot product.</returns>
        public double Dot(Vector2 rhs)
        {
            return (X * rhs.X) + (Y * rhs.Y);
        }

        /// <summary>
        /// Calculates the dot product between this vector and another vector.
        /// </summary>
        /// <param name="rhs">Another vector to use for the calculation of the dot product.</param>
        /// <returns>The dot product.</returns>
        public double Dot(ICoordinatePair<double> rhs)
        {
            return (X * rhs.X) + (Y * rhs.Y);
        }
        /// <summary>
        /// Calculates a vector resulting from adding the given vector to this vector.
        /// </summary>
        /// <param name="rhs">The vector to add.</param>
        /// <returns>A result of adding this vector with the given vector.</returns>
        public Vector2 Add(Vector2 rhs)
        {
            return new Vector2(
                X + rhs.X,
                Y + rhs.Y
            );
        }
        /// <summary>
        /// Calculates a vector resulting from adding the given point as a vector to this vector.
        /// </summary>
        /// <param name="rhs">The point to add as a vector.</param>
        /// <returns>A resulting vector.</returns>
        public Vector2 Add(Point2 rhs)
        {
            return new Vector2(
                X + rhs.X,
                Y + rhs.Y
            );
        }
        /// <summary>
        /// Calculates a vector resulting from adding the given vector to this vector.
        /// </summary>
        /// <param name="rhs">The vector to add.</param>
        /// <returns>A result of adding this vector with the given vector.</returns>
        public Vector2 Add(ICoordinatePair<double> rhs)
        {
            return new Vector2(
                X + rhs.X,
                Y + rhs.Y
            );
        }
        /// <summary>
        /// Calculates a vector resulting from adding the given vector to this vector.
        /// </summary>
        /// <param name="rhs">The vector to add.</param>
        /// <returns>A result of adding this vector with the given vector.</returns>
        IVector2<double> IVector2<double>.Add(ICoordinatePair<double> rhs)
        {
            return Add(rhs);
        }
        /// <summary>
        /// Calculates a vector oriented in the opposite direction.
        /// </summary>
        /// <returns>A vector with the same component values but different signs.</returns>
        public Vector2 GetNegative()
        {
            return new Vector2(-X, -Y);
        }
        /// <summary>
        /// Calculates a vector oriented in the opposite direction.
        /// </summary>
        /// <returns>A vector with the same component values but different signs.</returns>
        IVector2<double> IVector2<double>.GetNegative()
        {
            return GetNegative();
        }
        /// <summary>
        /// Calculates a vector with the same direction but a magnitude of one.
        /// </summary>
        /// <returns>A unit length vector.</returns>
        public Vector2 GetNormalized()
        {
            double m = GetMagnitude();
            return (
                0 == m
                ? ZeroVector
                : new Vector2(X / m, Y / m)
            );
        }
        /// <summary>
        /// Calculates a vector with the same direction but a magnitude of one.
        /// </summary>
        /// <returns>A unit length vector.</returns>
        IVector2<double> IVector2<double>.GetNormalized()
        {
            return GetNormalized();
        }
        /// <summary>
        /// Calculates the difference between this vector and another vector.
        /// </summary>
        /// <param name="rhs">The vector to subtract.</param>
        /// <returns>A result of subtracting the given vector from this vector.</returns>
        public Vector2 Difference(Vector2 rhs)
        {
            return new Vector2(X - rhs.X, Y - rhs.Y);
        }
        /// <summary>
        /// Calculates the difference between this vector and another vector.
        /// </summary>
        /// <param name="rhs">The vector to subtract.</param>
        /// <returns>A result of subtracting the given vector from this vector.</returns>
        public Vector2 Difference(ICoordinatePair<double> rhs)
        {
            return new Vector2(X - rhs.X, Y - rhs.Y);
        }
        /// <summary>
        /// Calculates the difference between this vector and another vector.
        /// </summary>
        /// <param name="rhs">The vector to subtract.</param>
        /// <returns>A result of subtracting the given vector from this vector.</returns>
        IVector2<double> IVector2<double>.Difference(ICoordinatePair<double> rhs)
        {
            return Difference(rhs);
        }
        /// <summary>
        /// Calculates the z-coordinate of the cross product of this vector and another vector defined by <paramref name="x"/> and <paramref name="y"/>.
        /// </summary>
        /// <param name="x">The x-coordinate of the other vector.</param>
        /// <param name="y">The y-coordinate of the other vector.</param>
        /// <returns>The z-coordinate of the cross product.</returns>
        public double Cross(double x, double y)
        {
            return (X * y) - (Y * x);
        }
        /// <summary>
        /// Calculates the z-coordinate of the cross product of this vector and another vector.
        /// </summary>
        /// <param name="rhs">A vector.</param>
        /// <returns>The z-coordinate of the cross product.</returns>
        public double Cross(Vector2 rhs)
        {
            return (X * rhs.Y) - (Y * rhs.X);
        }
        /// <summary>
        /// Calculates the z-coordinate of the cross product of this vector and another vector.
        /// </summary>
        /// <param name="rhs">A vector.</param>
        /// <returns>The z-coordinate of the cross product.</returns>
        public double Cross(ICoordinatePair<double> rhs)
        {
            return (X * rhs.Y) - (Y * rhs.X);
        }

        /// <summary>
        /// Calculates the magnitude of this vector.
        /// </summary>
        /// <returns>The magnitude.</returns>
        public double GetMagnitude()
        {
            return System.Math.Sqrt((X * X) + (Y * Y));
        }
        /// <summary>
        /// Calculates the squared magnitude of this vector.
        /// </summary>
        /// <returns>The squared magnitude.</returns>
        public double GetMagnitudeSquared()
        {
            return (X * X) + (Y * Y);
        }

        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <returns>
        /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
        /// </returns>
        /// <param name="obj">Another object to compare to.</param><filterpriority>2</filterpriority>
        public override bool Equals(object obj)
        {
            return null != obj
                && (
                    (obj is Vector2)
                    ? Equals((Vector2)obj)
                    : (obj is ICoordinatePair<double> && Equals(obj as ICoordinatePair<double>))
                )
            ;
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override int GetHashCode()
        {
            return X.GetHashCode();
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return String.Concat(X, ',', Y);
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(Vector2 other)
        {
            return X == other.X && Y == other.Y;
        }

        /// <summary>
        /// Indicates whether the current object is equal to another.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(ICoordinatePair<double> other)
        {
            return !ReferenceEquals(null,other) && X == other.X && Y == other.Y;
        }

        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings: 
        ///                     Value 
        ///                     Meaning 
        ///                     Less than zero 
        ///                     This object is less than the <paramref name="other"/> parameter.
        ///                     Zero 
        ///                     This object is equal to <paramref name="other"/>. 
        ///                     Greater than zero 
        ///                     This object is greater than <paramref name="other"/>. 
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public int CompareTo(Vector2 other)
        {
            int c = X.CompareTo(other.X);
            return 0 == c ? Y.CompareTo(other.Y) : c;
        }

        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings: 
        ///                     Value 
        ///                     Meaning 
        ///                     Less than zero 
        ///                     This object is less than the <paramref name="other"/> parameter.
        ///                     Zero 
        ///                     This object is equal to <paramref name="other"/>. 
        ///                     Greater than zero 
        ///                     This object is greater than <paramref name="other"/>. 
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public int CompareTo(ICoordinatePair<double> other)
        {
            if (null == other)
            {
                return 1;
            }
            int c = X.CompareTo(other.X);
            return 0 == c ? Y.CompareTo(Y) : c;
        }
        /// <summary>
        /// Creates a new vector which is scaled from this vector.
        /// </summary>
        /// <param name="factor">The scaling factor.</param>
        /// <returns>A scaled vector.</returns>
        public Vector2 GetScaled(double factor)
        {
            return new Vector2(X * factor, Y * factor);
        }
        /// <summary>
        /// Creates a new vector which is scaled from this vector.
        /// </summary>
        /// <param name="factor">The scaling factor.</param>
        /// <returns>A scaled vector.</returns>
        IVector2<double> IVector2<double>.GetScaled(double factor)
        {
            return GetScaled(factor);
        }

        double IHasDistance<double, IPoint2<double>>.Distance(IPoint2<double> p)
        {
            return new Segment2(Point2.ZeroPoint, this).Distance(p);
        }

        double IHasDistance<double, IPoint2<double>>.DistanceSquared(IPoint2<double> p)
        {
            return new Segment2(Point2.ZeroPoint, this).DistanceSquared(p);
        }

        bool IIntersectable<IPoint2<double>>.Intersects(IPoint2<double> p)
        {
            return new Segment2(Point2.ZeroPoint, this).Intersects(p);
        }

        IMbr<double> IHasMbr<double>.GetMbr()
        {
            return new Mbr(Point2.ZeroPoint, this);
        }

        IPoint2<double> IHasPlanarCentroid<double>.GetCentroid()
        {
            return Point2.ZeroPoint;
        }
        /// <summary>
        /// Gets a clock-wise perpendicular vector with the same magnitude as this vector.
        /// </summary>
        /// <returns>A vector.</returns>
        public Vector2 GetPerpendicularCw()
        {
            return new Vector2(Y, -X);
        }
        IVector2<double> IVector2<double>.GetPerpendicularCw()
        {
            return GetPerpendicularCw();
        }
        /// <summary>
        /// Gets a counter clock-wise perpendicular vector with the same magnitude as this vector.
        /// </summary>
        /// <returns>A vector.</returns>
        public Vector2 GetPerpendicularCcw()
        {
            return new Vector2(-Y, X);
        }
        IVector2<double> IVector2<double>.GetPerpendicularCcw()
        {
            return GetPerpendicularCcw();
        }


        bool IContainable<IMbr<double>>.Within(IMbr<double> mbr)
        {
            return new Mbr(Point2.ZeroPoint, this).Within(mbr);
        }
    }
}
